Jelajahi tipe literal templat TypeScript yang canggih untuk manipulasi, pencocokan pola, dan validasi string.
Template Literal Types: Pencocokan Pola dan Validasi String di TypeScript
Sistem tipe TypeScript terus berkembang, menawarkan pengembang alat yang lebih ampuh untuk mengekspresikan logika yang kompleks dan memastikan keamanan tipe. Salah satu fitur yang paling menarik dan serbaguna yang diperkenalkan dalam versi terbaru adalah template literal types. Tipe ini memungkinkan Anda memanipulasi string pada tingkat tipe, memungkinkan pencocokan pola dan validasi string tingkat lanjut. Ini membuka dunia kemungkinan baru untuk membuat aplikasi yang lebih kuat dan mudah dikelola.
Apa itu Template Literal Types?
Template literal types adalah bentuk tipe yang dibangun dengan menggabungkan tipe literal string dan tipe gabungan (union types), mirip dengan cara kerja template literal di JavaScript. Namun, alih-alih membuat string saat runtime, mereka membuat tipe baru berdasarkan tipe yang sudah ada.
Berikut adalah contoh dasar:
type Greeting<T extends string> = `Hello, ${T}!`;
type MyGreeting = Greeting<"World">; // type MyGreeting = "Hello, World!"
Dalam contoh ini, `Greeting` adalah template literal type yang mengambil tipe string `T` sebagai input dan mengembalikan tipe baru yang merupakan penggabungan dari "Hello, ", `T`, dan "!".
Pencocokan Pola String Dasar
Template literal types dapat digunakan untuk melakukan pencocokan pola string dasar. Ini memungkinkan Anda membuat tipe yang hanya valid jika sesuai dengan pola tertentu.
Misalnya, Anda dapat membuat tipe yang hanya menerima string yang diawali dengan "prefix-":
type PrefixedString<T extends string> = T extends `prefix-${string}` ? T : never;
type ValidPrefixedString = PrefixedString<"prefix-valid">; // type ValidPrefixedString = "prefix-valid"
type InvalidPrefixedString = PrefixedString<"invalid">; // type InvalidPrefixedString = never
Dalam contoh ini, `PrefixedString` menggunakan tipe kondisional untuk memeriksa apakah string input `T` diawali dengan "prefix-". Jika ya, tipenya adalah `T` itu sendiri; jika tidak, tipenya adalah `never`. `never` adalah tipe khusus di TypeScript yang mewakili tipe nilai yang tidak pernah terjadi, secara efektif mengecualikan string yang tidak valid.
Mengekstrak Bagian dari String
Template literal types juga dapat digunakan untuk mengekstrak bagian dari string. Ini sangat berguna ketika Anda perlu mengurai data dari string dan mengubahnya menjadi tipe yang berbeda.
Misalnya, Anda memiliki string yang mewakili koordinat dalam format "x:10,y:20". Anda dapat menggunakan template literal types untuk mengekstrak nilai x dan y:
type CoordinateString = `x:${number},y:${number}`;
type ExtractX<T extends CoordinateString> = T extends `x:${infer X},y:${number}` ? X : never;
type ExtractY<T extends CoordinateString> = T extends `x:${number},y:${infer Y}` ? Y : never;
type XValue = ExtractX<"x:10,y:20">; // type XValue = 10
type YValue = ExtractY<"x:10,y:20">; // type YValue = 20
Dalam contoh ini, `ExtractX` dan `ExtractY` menggunakan kata kunci `infer` untuk menangkap bagian dari string yang cocok dengan tipe `number`. `infer` memungkinkan Anda mengekstrak tipe dari pencocokan pola. Tipe yang ditangkap kemudian digunakan sebagai tipe pengembalian dari tipe kondisional.
Validasi String Tingkat Lanjut
Template literal types dapat digabungkan dengan fitur TypeScript lainnya, seperti tipe gabungan dan tipe kondisional, untuk melakukan validasi string tingkat lanjut. Ini memungkinkan Anda membuat tipe yang menegakkan aturan kompleks pada struktur dan konten string.
Misalnya, Anda dapat membuat tipe yang memvalidasi string tanggal ISO 8601:
type Year = `${number}${number}${number}${number}`;
type Month = `0${number}` | `10` | `11` | `12`;
type Day = `${0}${number}` | `${1 | 2}${number}` | `30` | `31`;
type ISODate = `${Year}-${Month}-${Day}`;
type ValidDate = ISODate extends "2023-10-27" ? true : false; // true
type InvalidDate = ISODate extends "2023-13-27" ? true : false; // false
function processDate(date: ISODate) {
// Logika fungsi di sini. TypeScript menegakkan format ISODate.
return `Processing date: ${date}`;
}
console.log(processDate("2024-01-15")); // Berhasil
//console.log(processDate("2024-1-15")); // Kesalahan TypeScript: Argumen tipe '"2024-1-15"' tidak dapat ditugaskan ke parameter tipe '`${number}${number}${number}${number}-${0}${number}-${0}${number}` | `${number}${number}${number}${number}-${0}${number}-${1}${number}` | ... 14 lagi ... | `${number}${number}${number}${number}-12-31`'.
Di sini, `Year`, `Month`, dan `Day` didefinisikan menggunakan template literal types untuk mewakili format yang valid untuk setiap bagian dari tanggal. `ISODate` kemudian menggabungkan tipe-tipe ini untuk membuat tipe yang mewakili string tanggal ISO 8601 yang valid. Contoh ini juga mendemonstrasikan bagaimana tipe ini dapat digunakan untuk menegakkan pemformatan data dalam sebuah fungsi, mencegah format tanggal yang salah untuk diteruskan. Ini meningkatkan keandalan kode dan mencegah kesalahan runtime yang disebabkan oleh input yang tidak valid.
Kasus Penggunaan Dunia Nyata
Template literal types dapat digunakan dalam berbagai skenario dunia nyata. Berikut adalah beberapa contoh:
- Validasi Formulir: Anda dapat menggunakan template literal types untuk memvalidasi format input formulir, seperti alamat email, nomor telepon, dan kode pos.
- Validasi Permintaan API: Anda dapat menggunakan template literal types untuk memvalidasi struktur payload permintaan API, memastikan bahwa mereka sesuai dengan format yang diharapkan. Misalnya, memvalidasi kode mata uang (misalnya, "USD", "EUR", "GBP").
- Penguraian File Konfigurasi: Anda dapat menggunakan template literal types untuk mengurai file konfigurasi dan mengekstrak nilai berdasarkan pola tertentu. Pertimbangkan untuk memvalidasi jalur file dalam objek konfigurasi.
- Enum Berbasis String: Anda dapat membuat enum berbasis string dengan validasi menggunakan template literal types.
Contoh: Memvalidasi Kode Mata Uang
Mari kita lihat contoh yang lebih rinci tentang memvalidasi kode mata uang. Kita ingin memastikan bahwa hanya kode mata uang ISO 4217 yang valid yang digunakan dalam aplikasi kita. Kode-kode ini biasanya terdiri dari tiga huruf kapital.
type CurrencyCode = `${Uppercase<string>}${Uppercase<string>}${Uppercase<string>}`;
function formatCurrency(amount: number, currency: CurrencyCode) {
// Logika fungsi untuk memformat mata uang berdasarkan kode yang disediakan.
return `$${amount} ${currency}`;
}
console.log(formatCurrency(100, "USD")); // Berhasil
//console.log(formatCurrency(100, "usd")); // Kesalahan TypeScript: Argumen tipe '"usd"' tidak dapat ditugaskan ke parameter tipe '`${Uppercase}${Uppercase}${Uppercase}`'.
//Contoh yang lebih tepat:
type ValidCurrencyCode = "USD" | "EUR" | "GBP" | "JPY" | "CAD" | "AUD"; // Tambahkan sesuai kebutuhan
type StronglyTypedCurrencyCode = ValidCurrencyCode;
function formatCurrencyStronglyTyped(amount: number, currency: StronglyTypedCurrencyCode) {
return `$${amount} ${currency}`;
}
console.log(formatCurrencyStronglyTyped(100, "EUR")); // Berhasil
//console.log(formatCurrencyStronglyTyped(100, "CNY")); // Kesalahan TypeScript: Argumen tipe '"CNY"' tidak dapat ditugaskan ke parameter tipe '"USD" | "EUR" | "GBP" | "JPY" | "CAD" | "AUD"'.
Contoh ini menunjukkan cara membuat tipe `CurrencyCode` yang hanya menerima string yang terdiri dari tiga karakter huruf kapital. Contoh kedua, yang lebih terstruktur tipenya, menunjukkan cara membatasi ini lebih lanjut ke daftar mata uang yang dapat diterima yang telah ditentukan sebelumnya.
Contoh: Memvalidasi Jalur Titik Akhir API
Kasus penggunaan lain adalah memvalidasi jalur titik akhir API. Anda dapat mendefinisikan tipe yang mewakili struktur titik akhir API yang valid, memastikan bahwa permintaan dibuat ke jalur yang benar. Ini sangat berguna dalam arsitektur layanan mikro di mana beberapa layanan mungkin mengekspos API yang berbeda.
type APIServiceName = "users" | "products" | "orders";
type APIEndpointPath = `/${APIServiceName}/${string}`;
function callAPI(path: APIEndpointPath) {
// Logika panggilan API
console.log(`Calling API: ${path}`);
}
callAPI("/users/123"); // Valid
callAPI("/products/details"); // Valid
//callAPI("/invalid/path"); // Kesalahan TypeScript
// Lebih spesifik lagi:
type APIAction = "create" | "read" | "update" | "delete";
type APIEndpointPathSpecific = `/${APIServiceName}/${APIAction}`;
function callAPISpecific(path: APIEndpointPathSpecific) {
// Logika panggilan API
console.log(`Calling specific API: ${path}`);
}
callAPISpecific("/users/create"); // Valid
//callAPISpecific("/users/list"); // Kesalahan TypeScript
Ini memungkinkan Anda untuk mendefinisikan struktur titik akhir API dengan lebih tepat, mencegah salah ketik dan memastikan konsistensi di seluruh aplikasi Anda. Ini adalah contoh dasar; pola yang lebih kompleks dapat dibuat untuk memvalidasi parameter kueri dan bagian lain dari URL.
Manfaat Menggunakan Template Literal Types
Menggunakan template literal types untuk pencocokan pola dan validasi string menawarkan beberapa manfaat:
- Keamanan Tipe yang Ditingkatkan: Template literal types memungkinkan Anda menegakkan batasan tipe yang lebih ketat pada string, mengurangi risiko kesalahan saat runtime.
- Keterbacaan Kode yang Ditingkatkan: Template literal types membuat kode Anda lebih mudah dibaca dengan jelas mengekspresikan format string yang diharapkan.
- Kemudahan Pemeliharaan yang Ditingkatkan: Template literal types membuat kode Anda lebih mudah dikelola dengan menyediakan satu sumber kebenaran untuk aturan validasi string.
- Pengalaman Pengembang yang Lebih Baik: Template literal types menyediakan penyelesaian otomatis dan pesan kesalahan yang lebih baik, meningkatkan pengalaman pengembang secara keseluruhan.
Batasan
Meskipun template literal types kuat, mereka juga memiliki beberapa batasan:
- Kompleksitas: Template literal types bisa menjadi kompleks, terutama ketika berurusan dengan pola yang rumit. Sangat penting untuk menyeimbangkan manfaat keamanan tipe dengan kemudahan pemeliharaan kode.
- Kinerja: Template literal types dapat memengaruhi kinerja kompilasi, terutama dalam proyek besar. Ini karena TypeScript perlu melakukan pemeriksaan tipe yang lebih kompleks.
- Dukungan Ekspresi Reguler Terbatas: Meskipun template literal types memungkinkan pencocokan pola, mereka tidak mendukung rentang penuh fitur ekspresi reguler. Untuk validasi string yang sangat kompleks, ekspresi reguler saat runtime mungkin masih diperlukan bersama dengan konstruksi tipe ini untuk sanitasi input yang tepat.
Praktik Terbaik
Berikut adalah beberapa praktik terbaik yang perlu diingat saat menggunakan template literal types:
- Mulai dari yang Sederhana: Mulailah dengan pola sederhana dan secara bertahap tingkatkan kompleksitas sesuai kebutuhan.
- Gunakan Nama Deskriptif: Gunakan nama deskriptif untuk template literal types Anda untuk meningkatkan keterbacaan kode.
- Dokumentasikan Tipe Anda: Dokumentasikan template literal types Anda untuk menjelaskan tujuan dan penggunaannya.
- Uji Secara Menyeluruh: Uji template literal types Anda secara menyeluruh untuk memastikan bahwa mereka berperilaku seperti yang diharapkan.
- Pertimbangkan Kinerja: Perhatikan dampak template literal types pada kinerja kompilasi dan optimalkan kode Anda sesuai kebutuhan.
Kesimpulan
Template literal types adalah fitur canggih di TypeScript yang memungkinkan Anda melakukan manipulasi string tingkat lanjut, pencocokan pola, dan validasi pada tingkat tipe. Dengan menggunakan template literal types, Anda dapat membuat aplikasi yang lebih kuat, mudah dikelola, dan aman tipenya. Meskipun memiliki beberapa batasan, manfaat menggunakan template literal types seringkali lebih besar daripada kerugiannya, menjadikannya alat yang berharga dalam persenjataan pengembang TypeScript mana pun. Seiring dengan terus berkembangnya bahasa TypeScript, memahami dan memanfaatkan fitur tipe lanjutan ini akan sangat penting untuk membangun perangkat lunak berkualitas tinggi. Ingatlah untuk menyeimbangkan kompleksitas dengan keterbacaan dan selalu utamakan pengujian menyeluruh.